// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace Microsoft.TeamFoundation.Migration.Toolkit.Wit
{
    /// <summary>
    /// Work item type object.
    /// </summary>
    class InternalWorkItemType
    {
        private object m_locker = new object();             // Synchronization object
        private SyncProcess m_sharedData;                   // Shared data object
        private Pair<string> m_names;                       // Names of the type on both sides
        private int m_lastId;                               // Id of the last added field definition

        private Pair<Dictionary<string, InternalFieldDefinition>> m_fields; // Field definitions
        private Pair<Dictionary<string, string>> m_exclusions;              // Explicit field exclusions
        private Pair<Dictionary<string, bool>> m_flatFields;                // Flat fields on both sides

        /// <summary>
        /// Creates a work item type object using settings from the config file.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="fieldMapping">Field mapping</param>
        /// <returns>Work item type object</returns>
        public static InternalWorkItemType Create(
            SyncProcess sharedData,
            WorkItemTypeMapping fieldMapping)
        {
            Pair<string> names = new Pair<string>(fieldMapping.TfsName, fieldMapping.OtherName);

            InternalWorkItemType wit = new InternalWorkItemType(sharedData, names);
            wit.Load(fieldMapping);

            if (sharedData.TfsSide == Side.Right)
            {
                wit.m_names = !wit.m_names;
                wit.m_fields = !wit.m_fields;
                wit.m_exclusions = !wit.m_exclusions;
                wit.m_flatFields = !wit.m_flatFields;
            }
            return wit;
        }

        /// <summary>
        /// Creates a work item type object that has no mapping in the config file.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="name">Type name</param>
        /// <returns>Work item type object</returns>
        public static InternalWorkItemType Create(
            SyncProcess sharedData,
            string name)
        {
            return new InternalWorkItemType(sharedData, new Pair<string>(name, name));
        }

        /// <summary>
        /// Gets valid field definition on the given side.
        /// </summary>
        /// <param name="name">Field name</param>
        /// <param name="side">Side</param>
        /// <returns>Field definition if the name is valid</returns>
        public InternalFieldDefinition GetFieldDefinition(
            string name,
            Side side)
        {
            // Is the name explicitly excluded?
            if (m_exclusions[side].ContainsKey(name))
            {
                return null;
            }

            InternalFieldDefinition f;
            lock (m_locker)
            {
                if (!m_fields[side].TryGetValue(name, out f))
                {
                    // Field definition is not cached yet.
                    if (!m_exclusions[!side].ContainsKey(name))
                    {
                        // Make sure the field exists on both sides
                        if (m_sharedData.Stores.Left.IsValidField(m_names.Left, name) &&
                            m_sharedData.Stores.Right.IsValidField(m_names.Right, name))
                        {
                            f = new InternalFieldDefinition(++m_lastId, name, IsFlat(m_sharedData.TfsSide, name, name));
                            m_fields.Left.Add(f.Names.Left, f);
                            m_fields.Right.Add(f.Names.Right, f);
                        }
                        else
                        {
                            m_exclusions.Left.Add(name, name);
                            m_exclusions.Right.Add(name, name);
                        }
                    }
                }
            }
            return f;
        }

        /// <summary>
        /// Returns shared data object.
        /// </summary>
        public SyncProcess SharedData { get { return m_sharedData; } }

        /// <summary>
        /// Returns names of the work item type.
        /// </summary>
        public Pair<string> Names { get { return m_names; } }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="names">Type names</param>
        private InternalWorkItemType(
            SyncProcess sharedData,
            Pair<string> names)
        {
            m_sharedData = sharedData;
            m_names = names;
            Side tfsSide = sharedData.TfsSide;
            var tfsStore = sharedData.Stores[tfsSide];
            var otherStore = sharedData.Stores[!tfsSide];

            m_fields = new Pair<Dictionary<string, InternalFieldDefinition>>(
                new Dictionary<string, InternalFieldDefinition>(tfsStore.FieldNameComparer),
                new Dictionary<string, InternalFieldDefinition>(otherStore.FieldNameComparer));
            m_exclusions = new Pair<Dictionary<string,string>>(
                new Dictionary<string,string>(tfsStore.FieldNameComparer),
                new Dictionary<string,string>(otherStore.FieldNameComparer));

            // Extract flat fields
            m_flatFields = new Pair<Dictionary<string, bool>>(
                new Dictionary<string, bool>(tfsStore.FieldNameComparer),
                new Dictionary<string, bool>(otherStore.FieldNameComparer));


            GetFlatFields(Side.Left, tfsStore, names[tfsSide]);
            GetFlatFields(Side.Right, otherStore, names[!tfsSide]);
        }

        /// <summary>
        /// Initializes the set of flat fields.
        /// </summary>
        /// <param name="side">Target side</param>
        /// <param name="store">Source work item store</param>
        /// <param name="workItemType">Source work item type</param>
        private void GetFlatFields(
            Side side,
            IMigrationWorkItemStore store,
            string workItemType)
        {
            IEnumerable<string> fields = store.GetFlatFields(workItemType);

            if (fields != null)
            {
                foreach (string field in fields)
                {
                    m_flatFields[side].Add(field, true);
                }
            }
        }

        /// <summary>
        /// Initializes work item type from the mapping.
        /// </summary>
        /// <param name="type">Field mapping</param>
        private void Load(
            WorkItemTypeMapping type)
        {
            if (!string.IsNullOrEmpty(type.FieldMapName))
            {
                FieldMap map = MigrationConfiguration.Current.Wit.FieldMaps[type.FieldMapName];

                // Load explicit exclusions
                for (int i = 0; i < map.Exclusions.Count; i++)
                {
                    FieldMapExclusion e = map.Exclusions[i];

                    if (e.System == SystemType.Tfs)
                    {
                        m_exclusions.Left.Add(e.FieldName, e.FieldName);
                    }
                    else
                    {
                        m_exclusions.Right.Add(e.FieldName, e.FieldName);
                    }
                }

                // Load explicit field mappings
                for (int i = 0; i < map.Fields.Count; i++)
                {
                    FieldMapEntry e = map.Fields[i];

                    InternalFieldDefinition fd = new InternalFieldDefinition(
                        m_sharedData, 
                        ++m_lastId, 
                        IsFlat(Side.Left, e.TfsName, e.OtherName), 
                        e);
                    m_fields.Left.Add(e.TfsName, fd);
                    m_fields.Right.Add(e.OtherName, fd);
                }
            }
        }

        /// <summary>
        /// Checks whether a field with the given names is flat.
        /// </summary>
        /// <param name="tfsSide">Side of the TFS adapter</param>
        /// <param name="tfsName">Field name on the TFS side</param>
        /// <param name="otherName">Field name on the non-TFS side</param>
        /// <returns>True if the field cannot be flat</returns>
        private bool IsFlat(Side tfsSide, string tfsName, string otherName)
        {
            return m_flatFields[tfsSide].ContainsKey(tfsName) || m_flatFields[!tfsSide].ContainsKey(otherName);
        }

        /// <summary>
        /// Tells whether a work item has any flat fields.
        /// </summary>
        public bool HasFlatFields { get { return m_flatFields.Left.Count != 0 || m_flatFields.Right.Count != 0; } }

        /// <summary>
        /// Returns a collection of flat fields.
        /// </summary>
        /// <returns>All flat fields</returns>
        public IEnumerable<InternalFieldDefinition> GetFlatFields()
        {
            var rightNames = new Dictionary<string, bool>(m_sharedData.Stores.Right.FieldNameComparer);

            // Process all flat fields from the left.
            foreach (string field in m_flatFields.Left.Keys)
            {
                var def = GetFieldDefinition(field, Side.Left);
                if (def != null)
                {
                    Debug.Assert(def.IsFlat, "The field is not flat!");
                    rightNames[def.Names.Right] = true;
                    yield return def;
                }
            }

            // Process all remaining fields on the right.
            foreach (string field in m_flatFields.Right.Keys)
            {
                if (!rightNames.ContainsKey(field))
                {
                    var def = GetFieldDefinition(field, Side.Right);
                    if (def != null)
                    {
                        Debug.Assert(def.IsFlat, "The field is not flat!");
                        yield return def;
                    }
                }
            }
        }
    }
}
